package com.github.common.extend.demo.config.filetask;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import java.io.*;
import java.text.MessageFormat;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * 文件生成任务管理器实现
 * @author wangjj7
 * @date 2023/8/2
 * @description
 */
@Slf4j
public class FileTaskManagerImpl implements FileTaskManager {

    private final Supplier<String> idGenerator;

    private final FileTaskProperties properties;

    //负责任务存储
    private final TaskStorage taskStorage;

    //负责文件存储
    private final FileStorage fileManager;

    private final ExecutorService executor;

    private final FileTaskManagerInterceptor interceptorChain;

    private static final ScheduledExecutorService MONITOR;

    static {
        MONITOR = new ScheduledThreadPoolExecutor(1 ,r -> {
            Thread thread = new Thread("文件任务监控线程");
            thread.setDaemon(true);
            return thread;
        });
    }

    public FileTaskManagerImpl(Supplier<String> idGenerator, FileTaskProperties properties,
                               TaskStorage taskStorage, FileStorage fileManager,
                               List<FileTaskManagerInterceptor> interceptors, ExecutorService executor)
    {
        Objects.requireNonNull(idGenerator);
        Objects.requireNonNull(properties);
        Objects.requireNonNull(taskStorage);
        Objects.requireNonNull(fileManager);

        this.idGenerator = idGenerator;
        this.properties = properties;
        this.taskStorage = taskStorage;
        this.fileManager = fileManager;
        this.executor = executor;
        this.interceptorChain = new FileTaskManagerInterceptor.InterceptorChain(interceptors);

        init();
    }

    private void init(){
        registerHeartbeatTimeout();
    }

    private void registerHeartbeatTimeout()
    {
        log.info("【文件任务心跳超时监听】注册");
        MONITOR.scheduleAtFixedRate(() -> taskStorage.heartbeatTimeout(
                properties.getHeartbeatTimeout() ,ChronoUnit.SECONDS),
                properties.getHeartbeatTimeout(),
                properties.getHeartbeatTimeout(),
                TimeUnit.SECONDS);
    }

    @Override
    public FileTask submit(FileTaskParam fileTaskParam)
    {
        valid(fileTaskParam);

        String id = fileTaskParam.getId();
        ScheduledFuture<?> heartbeatScheduledFuture = null;
        if (MONITOR != null) {
            //注册心跳
            heartbeatScheduledFuture = MONITOR.scheduleAtFixedRate(
                    () -> taskStorage.refreshHeartbeatTime(id),
                    properties.getHeartbeatInterval(),
                    properties.getHeartbeatInterval(),
                    TimeUnit.SECONDS);
            log.info("任务【{}】注册心跳监听，心跳间隔{}秒，心跳超时时长{}秒" ,
                    id ,properties.getHeartbeatInterval() ,properties.getHeartbeatTimeout());
        }

        FileTask resultTaskDTO = null;
        Exception resultException = null;
        try {
            interceptorChain.preExe(fileTaskParam);
            resultTaskDTO = submit0(fileTaskParam);
            return resultTaskDTO;
        }catch (Exception e){
            resultException = e;
            throw e;
        }finally {
            if (heartbeatScheduledFuture != null) {
                heartbeatScheduledFuture.cancel(true);
                log.info("任务【{}】结束,心跳监听移除" ,id);
            }
            interceptorChain.postExe(resultTaskDTO ,resultException);
        }
    }

    private FileTask submit0(FileTaskParam fileTaskParam)
    {
        log.info("提交任务,{}" , fileTaskParam);
        LocalDateTime createTime = LocalDateTime.now();

        String id = fileTaskParam.getId();

        FileTask FileTask = new FileTask();
        BeanUtils.copyProperties(fileTaskParam, FileTask.class);
        FileTask.setGmtCreated(createTime);
        FileTask.setHeartbeatTime(createTime);
        FileTask.setStatus(FileTaskStatus.UNDONE.getCode());

        Integer expiredDays = fileTaskParam.getExpiredDays();
        if (expiredDays == null){
            expiredDays = properties.getDefaultExpiredDays();
        }
        if (expiredDays > 0){
            FileTask.setExpiredTime(createTime.plusDays(expiredDays));
        }

        boolean hasShare = false;
        if (Boolean.TRUE.equals(fileTaskParam.getCanShare())) {
            hasShare = tryShare(FileTask);
        }

        taskStorage.save(FileTask);
        if (hasShare){
            FileTask returnTaskDTO = get(id);
            return returnTaskDTO.setFileSupplier(() -> localLoading(returnTaskDTO));
        }

        return execute(FileTask, fileTaskParam.getDataSourceBuilder());
    }

    //本地加载
    private InputStream localLoading(FileTask taskDTO)
    {
        String id = taskDTO.getId();
        while (FileTaskStatus.UNDONE.getCode().equals(taskDTO.getStatus())){
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new IllegalStateException(MessageFormat.format("任务【{0}】中断，取消轮询等待" ,id));
            }
            taskDTO = get(id);
        }
        String fileId = taskDTO.getFileId();
        if (FileTaskStatus.SUCCESS.getCode().equals(taskDTO.getStatus())
                && StringUtils.isNotBlank(fileId))
        {
            if (FileTaskStatus.SUCCESS.getCode().equals(taskDTO.getStatus()) && StringUtils.isNotBlank(fileId))
            {
                return fileManager.loadFileStream(fileId);
            }
        }
        throw new IllegalStateException(MessageFormat.format(
                "共享任务【{}】状态异常无法加载" ,taskDTO.getShareTaskId()));
    }

    private void valid(FileTaskParam fileTaskParam)
    {
        if (fileTaskParam.getId() == null){
            fileTaskParam.setId(String.valueOf(idGenerator.get()));
        }
        if (StringUtils.isBlank(fileTaskParam.getModelName())){
            throw new IllegalArgumentException("modelName必须定义");
        }
        if (fileTaskParam.getDataSourceBuilder() == null){
            throw new IllegalArgumentException("dataSourceBuilder必须定义");
        }
        if (fileTaskParam.getCanShare() == null){
            fileTaskParam.setCanShare(properties.isShare());
        }
        if (fileTaskParam.getExpiredDays() == null || fileTaskParam.getExpiredDays() < 1){
            fileTaskParam.setExpiredDays(properties.getDefaultExpiredDays());
        }
        if (StringUtils.isBlank(fileTaskParam.getTaskName())){
            fileTaskParam.setTaskName(fileTaskParam.getModelName() + "-" + fileTaskParam.getId());
        }
        if (StringUtils.isBlank(fileTaskParam.getFileName())){
            fileTaskParam.setTaskName(fileTaskParam.getFileName());
        }
    }

    private FileTask execute(FileTask fileTask, DataSourceBuilder dataSourceBuilder)
    {
        try {
            return buildFileAndSave(fileTask,dataSourceBuilder);
        }catch (Exception e){
            taskStorage.fail(fileTask,"未知异常" ,e);
            throw e;
        }
    }

    //生成文件并保存
    private FileTask buildFileAndSave(FileTask fileTask, DataSourceBuilder dataSourceBuilder)
    {
        String id = fileTask.getId();
        LocalDateTime fileBuildStartTime = LocalDateTime.now();
        int byteLength = -1;
        try {
            dataSourceBuilder.buildSource(new Consumer<Double>() {
                @Override
                public void accept(Double rate) {
                    refreshFileBuildRate(rate , fileTask);
                }
            });
            fileTask.setFileSize(byteLength  = dataSourceBuilder.length());
        }catch (Exception e){
            taskStorage.fail(fileTask,"生成文件失败" ,e);
            throw new IllegalStateException("文件生成失败" ,e);
        }

        Supplier<InputStream> fileSupplier = () -> dataSourceBuilder.inputStream();
        interceptorChain.postFileBuild(fileSupplier , fileTask);

        LocalDateTime fileBuildEndTime = LocalDateTime.now();
        long fileBuildConsumingMS = Duration.between(fileBuildStartTime, fileBuildEndTime).toMillis();
        log.info("生成任务【{}】文件，大小【{}】字节,耗时【{}】毫秒" ,id ,byteLength ,fileBuildConsumingMS);

        FileInfo fileInfo = null;
        try {
            fileInfo = fileManager.save(new FileInfoParam()
                    .setFileSupplier(fileSupplier)
                    .setByteSize(fileTask.getFileSize())
                    .setCreateUser(fileTask.getCreateUser())
                    .setExpiredTime(fileBuildEndTime.plusDays(properties.getDefaultExpiredDays()))
                    .setOriginName(fileTask.getFileName()));
        } catch (Exception e) {
            log.info("文件保存异常" ,e);
            taskStorage.fail(fileTask ,"文件上传异常" ,e);
            return get(fileTask.getId()).setFileSupplier(fileSupplier);
        }

        fileTask.setFileId(fileInfo.getId());
        taskStorage.success(fileTask);
        return get(id).setFileSupplier(fileSupplier);
    }

    @Override
    public Future<FileTask> asyncSubmit(FileTaskParam fileTaskParam, FileTaskCallback fileTaskCallback)
    {
        if (executor == null){
            throw new IllegalStateException("没有配置异步执行器executor");
        }
        return executor.submit(() -> {
            try {
                fileTaskCallback.preExe(fileTaskParam);
            }catch (Exception e){
                log.error("文件任务执行前回调异常,{}" ,fileTaskParam ,e);
            }

            FileTask resultTaskDTO = null;
            Exception ex = null;
            try {
                resultTaskDTO = submit(fileTaskParam);
            }catch (Exception e){
                ex = e;
                throw e;
            }finally {
                if (fileTaskCallback != null) {
                    try {
                        fileTaskCallback.postExe(resultTaskDTO ,ex);
                    }catch (Exception e){
                        log.error("文件任务执行前回调异常,{}" ,resultTaskDTO ,e);
                    }
                }
            }
            return resultTaskDTO;
        });
    }

    @Override
    public FileTask get(String id){
        return convert(taskStorage.get(id));
    }

    //尝试复用任务
    private boolean tryShare(FileTask fileTask)
    {
        FileTask shareTaskDTO = taskStorage.getShare(fileTask);

        if (shareTaskDTO != null && StringUtils.isNotBlank(shareTaskDTO.getFileId()))
        {
            String fileId = shareTaskDTO.getFileId();
            FileInfo fileInfo = fileManager.get(fileId);

            if (fileInfo == null || fileInfo.isDeleted()){
                return false;
            }

            LocalDateTime oldExpiredTime = fileInfo.getExpiredTime();
            LocalDateTime newExpiredTime = fileTask.getExpiredTime();
            if (newExpiredTime == null || (oldExpiredTime != null && oldExpiredTime.isBefore(newExpiredTime)))
            {
                fileManager.overtimeExpiredTime(fileId, newExpiredTime);
            }

            fileTask.setStatus(FileTaskStatus.SHARE.getCode());
            fileTask.setShareTaskId(shareTaskDTO.getId());

            log.info("共享任务执行结果,{}" , fileTask);
            return true;
        }

        return false;
    }

    private void refreshFileBuildRate(double fileBuildRate , FileTask fileTask)
    {
        String id = fileTask.getId();
        fileBuildRate = ((int) ((fileBuildRate > 100 ? 100d : fileBuildRate) * 99)) / 100;//预留1%的时间上传
        log.info("文件任务【{}】文件生成进度{}%" ,id ,fileBuildRate);
        taskStorage.refreshRate(id ,fileBuildRate);
    }

    private void updateFileStatus(FileTask FileTask, Function<String , FileInfo> xFileInfoFunction)
    {
        String fileId = FileTask.getFileId();
        if (StringUtils.isNotBlank(fileId) && FileTaskStatus.SUCCESS.getCode().equals(FileTask.getStatus())) {
            FileInfo fileInfo = xFileInfoFunction.apply(fileId);
            if (fileInfo != null){
                FileTask.setExpiredTime(fileInfo.getExpiredTime());
                FileTask.setFileUrl(fileInfo.getUrl());
                FileTask.setFileDeleteTime(fileInfo.getDeleteTime());
                if (FileTask.getFileSupplier() != null){
                    FileTask.setFileSupplier(() -> fileManager.loadFileStream(fileId));
                }
            }
        }
    }

    private FileTask convert(FileTask fileTask)
    {
        String fileId = fileTask.getFileId();
        if (StringUtils.isNotBlank(fileId) && FileTaskStatus.SUCCESS.getCode().equals(fileTask.getStatus())){
            updateFileStatus(fileTask, id -> fileManager.get(id));
        }
        return fileTask;
    }

    private List<FileTask> convert(List<FileTask> fileTasks)
    {
        String[] fileIds = fileTasks.stream()
                .filter(x -> FileTaskStatus.SUCCESS.getCode().equals(x.getStatus())
                        && StringUtils.isNotBlank(x.getFileId()))
                .map(FileTask::getFileId)
                .distinct()
                .toArray(length -> new String[length]);

        Map<String , FileInfo> fileMap = fileManager.get(fileIds)
                .stream()
                .collect(Collectors.toMap(FileInfo::getId ,Function.identity() ,(v0 , v1) -> v0));

        for (FileTask FileTask : fileTasks) {
            updateFileStatus(FileTask, id -> fileMap.get(id));
        }
        return fileTasks;
    }

}
